/**
 * Copyright (C) 2021 - 2031 O-Cubes Co., Ltd.
 */

/****************************************************************
 *  @file    efuse.c
 *  @brief   efuse source file
 *  @version v1.0
 *  @date    03. Apr. 2023
 ****************************************************************/

#include "efuse.h"
#include "sec.h"

static inline void efuse_pgm_data(uint32_t data)
{
	EFUSE_REG_PTR()->input = data;
}

static inline void efuse_pgm_offset(uint32_t addr)
{
	EFUSE_REG_PTR()->offset = (addr >> 2);
}

static inline void efuse_write_en(void)
{
	EFUSE_REG_PTR()->ctrl |= EFUSE_WR_EN;
}

static inline void efuse_write_dis(void)
{
	EFUSE_REG_PTR()->ctrl &= ~EFUSE_WR_EN;
}

static inline void efuse_pgm_en(void)
{
	EFUSE_REG_PTR()->ctrl |= EFUSE_PGM_EN;
}

static inline void efuse_pgm_dis(void)
{
	EFUSE_REG_PTR()->ctrl &= ~EFUSE_PGM_EN;
}

static inline int32_t efuse_wait_pgm_done(uint32_t timeout)
{
	return wait_cond_timeout(EFUSE_REG_PTR()->mis & EFUSE_PGM_DONE, timeout);
}

static inline void efuse_clr_pgm_status(void)
{
	EFUSE_REG_PTR()->icr |= EFUSE_PGM_DONE;
}

static inline int32_t efuse_wait_rd_done(uint32_t timeout)
{
	return wait_cond_timeout(EFUSE_REG_PTR()->mis & EFUSE_RD_DONE, timeout);
}

static inline uint32_t efuse_get_rd_status(void)
{
	return EFUSE_REG_PTR()->mis & EFUSE_RD_DONE;
}

static inline void efuse_clr_rd_status(void)
{
	EFUSE_REG_PTR()->icr |= EFUSE_RD_DONE;
}

static inline void efuse_read_en(void)
{
	EFUSE_REG_PTR()->ctrl |= EFUSE_RD_EN;
}

static inline void efuse_read_dis(void)
{
	EFUSE_REG_PTR()->ctrl &= ~EFUSE_RD_EN;
}

static inline uint32_t efuse_read_data(uint32_t idx)
{
	return EFUSE_REG_PTR()->data[idx];
}

/**
 * Write eFuse.
 * @addr: addr to be write to eFuse, should align to 4-byte boundary.
 * @len: the length of the data, should align to 4-byte boundary.
 * return: -1 if error, or 0 for success.
 */
int32_t efuse_write(uint32_t addr, uint8_t *data, uint32_t len)
{
	uint32_t i;

	if (!data || addr & 0x3 || len & 0x3 || addr + len > EFUSE_BIT_SIZE / 8)
		return -1;

	for (i = 0; i < len; i += 4) {
		efuse_pgm_offset(addr + i);
		efuse_pgm_data(*(uint32_t *)(data + i));
		efuse_write_en();
		efuse_write_dis();
	}

	efuse_pgm_en();

	if (efuse_wait_pgm_done(10))
		return -1;

	efuse_clr_pgm_status();

	efuse_pgm_dis();

	return 0;
}

/**
 * Read eFuse.
 * @addr: addr to be read from eFuse, should align to 4-byte boundary.
 * @len: the length of the data, should align to 4-byte boundary.
 * return: -1 if error, or 0 for success.
 */
int32_t efuse_read(uint32_t addr, uint8_t *data, uint32_t len)
{
	uint32_t value[8];
	uint32_t offset;
	uint8_t i;

	if (!data || addr & 0x3 || len & 0x3 || (addr + len > EFUSE_BIT_SIZE / 8))
		return -1;

	if (0 != efuse_get_rd_status())
		efuse_clr_rd_status();

	efuse_read_en();

	if (efuse_wait_rd_done(10))
		return -1;

	for (i = 0; i < 8; i++)
		value[i] = efuse_read_data(i);

	efuse_clr_rd_status();
	efuse_read_dis();

	offset = addr / 4;
	for (i = 0; i < len; i += 4) {
		data[i + 0] =  value[offset] & 0x000000ff;
		data[i + 1] = (value[offset] & 0x0000ff00) >> 8;
		data[i + 2] = (value[offset] & 0x00ff0000) >> 16;
		data[i + 3] = (value[offset] & 0xff000000) >> 24;
		offset++;
	}

	return 0;
}

